function Foo() {}

// 1.【构造函数】
// 用来初始化新创建的对象的函数是构造函数。在例子中，Foo()函数是构造函数

// 2.【实例对象】
// 通过构造函数的new操作创建的对象是实例对象。可以用一个构造函数，构造多个实例对象

var f1 = new Foo()
var f2 = new Foo()

console.log('f1 === f2 是否相等？ =>', f1 === f2) //false

// 3.【原型对象以及prototype】
// 构造函数有一个prototype属性，指向实例对象的原型对象。
// 通过同一个构造函数实例化的多个对象具有相同的原型对象。经常使用原型对象来实现继承

Foo.prototype.a = 1
var f3 = new Foo()
var f4 = new Foo()

console.log('Foo.prototype.a =>', Foo.prototype.a) // = 1
console.log('f3.a =>', f3.a)
console.log('f4.a =>', f4.a)
console.log('Foo =>', Foo)

// 4.【constructor】
// 原型对象有一个constructor属性，指向该原型对象对应的构造函数

let res = Foo.prototype.constructor === Foo
console.log('Foo.prototype.constructor === Foo 是否相等? ', res)

//由于实例对象可以继承原型对象的属性，
//所以实例对象也拥有constructor属性，同样指向原型对象对应的构造函数

//由于 f1 继承原型对象 Foo.prototype 的constructor，所以...
let res1 = f1.constructor === Foo
console.log('f1.constructor === Foo 是否相等? ', res1)

let res2 = f1.__proto__.constructor === Foo
console.log('f1.__proto__.constructor === Foo 是否相等? ', res2)

//【__proto__】
// 实例对象有一个__proto__属性，指向该实例对象对应的原型对象
let res3 = f1.__proto__ === Foo.prototype
console.log('f1.__proto__ === Foo.prototype 是否相等? ', res3)

console.log(f1)

console.log('Foo.prototype.__proto__ -> 原型对象', Foo.prototype.__proto__)

let res4 = Foo.prototype.__proto__ === Object.prototype
console.log('Foo.prototype.__proto__ === Object.prototype', res4)

console.log('Foo.prototype.__proto__', Foo.prototype.__proto__)
console.log('Object.prototype', Object.prototype)

let res5 = Foo.prototype.__proto__.constructor
console.log('Foo.prototype.__proto__.constructor === Object', res5 === Object)

console.log('Object.prototype.__proto__', Object.prototype.__proto__)

// 任何函数都可以看做是通过Function()构造函数的new操作实例化的结果
// 如果把函数Foo当成实例对象的话，其构造函数是Function()，其原型对象是Function.prototype；
// 类似地，函数Object的构造函数也是Function()，其原型对象是Function.prototype

console.log(Foo.__proto__ === Function.prototype)
console.log(Object.__proto__ === Function.prototype)

// 原型对象Function.prototype的constructor属性指向构造函数Function()；
// 实例对象Object和Foo本身没有constructor属性，需要继承原型对象Function.prototype的constructor属性

console.log('==============================================')

console.log(Function.prototype.constructor === Function) //true
console.log(Foo.constructor === Function) //true
console.log(Object.constructor === Function) //true

console.log(Foo.hasOwnProperty('constructor')) //false
console.log(Object.hasOwnProperty('constructor')) //false

console.log('==============================================')

// 所有的函数都可以看成是构造函数Function()的new操作的实例化对象。
// 那么，Function可以看成是调用其自身的new操作的实例化的结果
// 所以，如果Function作为实例对象，其构造函数是Function，其原型对象是Function.prototype

console.log(Function.__proto__ === Function.prototype) //true
console.log(Function.prototype.constructor === Function) //true
console.log(Function.prototype === Function.prototype) //true

console.log('==============================================')

// 如果Function.prototype作为实例对象的话，其原型对象是什么呢？
// 和前面一样，所有的对象都可以看成是Object()构造函数的new操作的实例化结果。
// 所以，Function.prototype的原型对象是Object.prototype，其原型函数是Object()

console.log(Function.prototype.__proto__) //true
console.log(Function.prototype.__proto__ === Object.prototype) //true

// Object.prototype的原型对象是null
console.log(Object.prototype.__proto__ === null)

// 总结：
// 【1】函数(Function也是函数)是new Function的结果，所以函数可以作为实例对象，其构造函数是Function()，原型对象是Function.prototype
// 【2】对象(函数也是对象)是new Object的结果，所以对象可以作为实例对象，其构造函数是Object()，原型对象是Object.prototype
// 【3】Object.prototype的原型对象是null

console.log('==============================================')

// New操作符
//

function Person(name, age) {
  this.name = name
  this.age = age
}
const person1 = new Person('Tom', 20)
console.log(person1) // Person {name: "Tom", age: 20}

// new关键字进行的操作
// new关键字进行了如下的操作（为了便于描述，obj用来表示创建的空对象、用constrc来表示构造函数）：

// 1. 创建一个空对象obj（{}）；
// 2. 将obj的[[prototype]]属性指向构造函数constrc的原型（即obj.[[prototype]] = constrc.prototype）。
// 3.将构造函数constrc内部的this绑定到新建的对象obj，执行constrc（也就是跟调用普通函数一样，
// 只是此时函数的this为新创建的对象obj而已，就好像执行obj.constrc()一样）；
// 4.若构造函数没有返回非原始值（即不是引用类型的值），则返回该新建的对象obj（默认会添加return this）。
// 否则，返回引用类型的值。

function myNew(constrc, ...args) {
  // 类型判断，错误处理
  if (typeof constrc !== 'function') {
    throw '构造函数第一个参数应为函数'
  }

  // 1,2 创建一个对象obj，将obj的__proto__属性指向构造函数的原型对象
  // 即实现：obj.__proto__ === constructor.prototype
  const obj = Object.create(constrc.prototype)

  console.log(Object.getPrototypeOf(obj) === constrc.prototype)
  console.log(obj.constructor)
  // 等价于 ==> const obj = {};
  // obj.__proto__ = constrc.prototype;
  // obj.constructor = constrc

  // __proto__ 是浏览器实现的查看原型方案。
  // 用[ES5]则是：
  // Object.getPrototypeOf(obj) === constrc.prototype; // true

  // 3.将constrc内部的this（即执行上下文）指向obj，并执行
  const result = constrc.apply(obj, args)
  // 4. 如果构造函数返回的是对象，则使用构造函数执行的结果。否则，返回新创建的对象
  return result instanceof Object ? result : obj
}

// 使用的例子：
function Person(name, age) {
  this.name = name
  this.age = age
}
const person2 = myNew(Person, 'Tom', 20)
console.log(person2) // Person {name: "Tom", age: 20}
